1

本节将实现文章、评论与用户关联的功能。

关系定义

首先修改 postscomments 表,增加 user_id 字段

/database/migrations/2017_04_12_124622_create_posts_table.php
/database/migrations/2017_04_15_062905_create_comments_table.php
public function up()
{
    Schema::create('posts', function (Blueprint $table) {
         
           // 增加
        $table->integer('user_id')->unsigned();
     
        $table->foreign('user_id')
              ->references('id')
              ->on('users')
              ->onDelete('cascade');
    });
}

全部回滚并重新执行迁移:

$ php artisan migrate:refresh

添加用户表与文章表、评论表的一对多关系:

/app/User.php
public function posts()
{
    return $this->hasMany(\App\Post::class);
}

public function comments()
{
    return $this->hasMany(\App\Comment::class);
}

添加文章、评论表与用户表的多对一关系:

/app/Comment.php
/app/Post.php
public function user()
{
    return $this->belongsTo(\App\User::class);
}

同时,评论表的 $fillable 字段增加 user_id

注册

首先,定义处理注册相关业务的控制器:

$ php artisan make:controller RegistrationController

定义路由响应注册请求:

Route::get('/register','RegistrationController@create');

定义方法,返回注册页面视图:

public function create()
{
    return view('registration.create');
}

创建注册页面:

/resources/views/registration/create.blade.php
@extends('layouts.master')

@section('content')
    
    <div class="col-sm-8 blog-main">
        
        <form method="post" for="/register"> 
            {{ csrf_field() }}
            <fieldset class="form-group">
                <label for="name">用户名:</label>
                <input type="text" name="name" id="name" class="form-control" required>
            </fieldset>
            <fieldset class="form-group">
                <label for="email">邮箱</label>
                <input type="email" class="form-control" name="email" id="email" required>
                <small class="text-muted">我们不会与别人分享您的邮箱</small>
            </fieldset>
            <fieldset class="form-group">
                <label for="password">密码:</label>
                <input type="password" name="password" class="form-control" id="password" >
            </fieldset>    
            <fieldset class="form-group">
                <label for="password_confirmation">再次输入密码:</label>
                <input type="password" name="password_confirmation" class="form-control" id="password_confirmation">
            </fieldset>    
            <button type="submit" class="btn btn-primary">提交</button>
        </form>

    </div>

@endsection

定义路由响应注册提交:

Route::post('/register','RegistrationController@store');

定义方法处理注册提交:

/app/Http/Controllers/RegistrationController.php
use App\User;
public function store()
{    
    $this->validate(request(),[
        'name' => 'required',
        'email' => 'required|email',
        'password' => 'required|confirmed',
    ]);

    $user = User::create(request(['name','password','email']));

    auth()->login($user);

    return redirect()->home();
}

该方法包括了四部分:

  1. 验证字段,其中 password 使用了 confirmed 验证规则,会自动去匹配 xxxxxx_confirmation 是否一致,因此之前的视图要按照规范命名好。

  2. 创建用户

  3. 登录该用户

  4. 返回名字为「home」的路由

其中,我们需要为路由命名,以匹配第四步:

Route::get('/posts','PostsController@index')->name('home');

虽然完成了注册功能,但是我们保存密码使用的明文,我们可以定义一个修改器,让每次保存密码时都自动加密:

/app/User.php
public function setPasswordAttribute($password)
{
    $this->attributes['password'] = bcrypt($password);
}

登录

创建控制器,处理用户登录业务:

$ php artisan make:controller SessionsController

用户访问 /login 时,路由分发该请求:

Route::get('/login','SessionsController@create');

create 方法返回用户登录页面视图:

/resources/views/sessions/create.blade.php
@extends('layouts.master')

@section('content')
    
    <div class="col-sm-8 blog-main">
        
        <form method="post" for="/login"> 
            {{ csrf_field() }}
            <fieldset class="form-group">
                <label for="email">邮箱</label>
                <input type="email" class="form-control" name="email" id="email" required>
            </fieldset>
            <fieldset class="form-group">
                <label for="password">密码:</label>
                <input type="password" name="password" class="form-control" id="password" >
            </fieldset>    
            <button type="submit" class="btn btn-primary">登录</button>
        </form>

    </div>

@endsection

用户点击登录后,路由分发该请求:

Route::post('/login','SessionsController@store');

最后是控制器对登录行为进行处理:

/app/Http/Controllers/SessionsController.php
public function store()
{    

    if (!auth()->attempt(request(['email', 'password']))) {
        return back()->withErrors([
            'messages' => '请确保邮箱和密码正确!'
        ]);
    }

    return redirect()->home();
} 

我们使用了 Auth 类提供的 attempt() 进行验证,只需要传入 emailpassword 即可,attempt 方法会对密码经过加密后与数据库进行比较,若匹配则
开启一个通过认证的 session 给用户。同时,我们还自定义了返回的错误信息。

登出

登出的实现比较简单,首先是路由:

Route::get('/logout','SessionsController@destroy');

控制器:

public function destroy()
{
    auth()->logout();

    return redirect()->home();
}

最后,我们优化下导航让,令其根据用户登录信息来显示不同的设置项:

<div class="blog-masthead">
  <div class="container">
    <nav class="nav blog-nav">
      <a class="nav-link active" href="#">Home</a>
      <a class="nav-link" href="#">New features</a>
      <a class="nav-link" href="#">Press</a>
      <a class="nav-link" href="#">New hires</a>
      <a class="nav-link" href="#">About</a>
      
      <ul class="nav nav-tabs ml-auto">
        <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">设置</a>
            <div class="dropdown-menu">
                @if (Auth::check())
                    <a class="dropdown-item" href="#">{{ Auth::user()->name }}</a>
                    <a class="dropdown-item" href="/logout">登出</a>
                @else
                    <a class="dropdown-item" href="/login">登录</a>
                    <a class="dropdown-item" href="/register">注册</a>
                @endif

            </div>
        </li>
    </ul>

    </nav>
  </div>
</div>

<script type="text/javascript">
    $('.dropdown-toggle').dropdown()
</script>

注意,如果要让下拉框生效,需要引入相关的 js

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/js/bootstrap.js"></script>

权限控制

实现了登录与登出功能,就可以对用户行为进行权限控制了。

首先是文章的权限控制,对于「未登录」的用户,只可以阅读文章,因此可以直接使用 Laravel 提供的中间件来实现:

/app/Http/Controllers/PostsController.php
public function __construct()
{
    $this->middleware('auth')->except(['index','show']);
}

意思是只有授权的用户才能够访问其他请求,除了 indexshow 外。

然后是用户的权限控制:

/app/Http/Controllers/SessionsController.php
public function __construct()
{
    $this->middleware('guest')->except(['destroy']);
}

意思是只有游客才能访问其他请求,除了 destroy

完善文章与评论的创建

最后,完善文章与评论的创建功能,绑定用户 id。首先是文章的创建:

/app/Http/Controllers/PostsController.php
public function store(Request $request)
{
    $this->validate(request(), [
        'title' => 'required|unique:posts|max:255',
        'body' => 'required|min:5',
    ]);

    $post = new Post(request(['title', 'body']));

    auth()->user()->publishPost($post);

    return redirect("posts");
}

创建文章直接使用关系模型:

/app/User.php
public function publishPost(Post $post)
{
    $this->posts()->save($post);
}

然后是评论的创建:

public function store(Post $post)
{    
    $this->validate(request(),[
        'body' => 'required|min:5'
        ]);

    $post->addComment(new Comment([
        'user_id' => auth()->user()->id,
        'body'       => request('body'),

        ]));
    return back();
}

同样使用关系模型:

/app/Post.php
public function addComment(Comment $comment)
{    
    $this->comments()->save($comment);
}

最后,是一些视图的更新:

文章列表中,绑定作者:

/resources/views/posts/index.blade.php
 <p class="blog-post-meta">{{ $post->created_at->toFormattedDateString() }} by <a href="#">{{$post->user->name}}</a></p>

具体文章与评论显示时,也绑定作者:

/resources/views/posts/show.blade.php
<div class="blog-post">
    <h2 class="blog-post-title">{{ $post->title }}</h2>
    <p class="blog-post-meta">{{ $post->created_at->toFormattedDateString() }} by <a href="#">{{ $post->user->name }}</a></p>
    <p>{{$post->body}}</p>
 </div>

 @foreach ($post->comments as $comment)
    <div class="card">
        <div class="card-header">
            {{$comment->created_at->diffForHumans() }}
        </div>
        <div class="card-block">
            <p class="card-text">{{ $comment->body }}</p>
             <p class="card-text"><small class="text-muted">by {{$comment->user->name }}</small></p>
         </div>
    </div>

    <br>
@endforeach


心智极客
1k 声望645 粉丝